package com.yxsk.relay.job.component.endpoint.registry;

import com.yxsk.relay.job.component.common.constant.NetProtocol;
import com.yxsk.relay.job.component.common.constant.UriConstant;
import com.yxsk.relay.job.component.common.protocol.caller.RemoteCaller;
import com.yxsk.relay.job.component.common.protocol.message.base.ResultResponse;
import com.yxsk.relay.job.component.common.protocol.message.renewal.RenewalRequest;
import com.yxsk.relay.job.component.common.protocol.message.termination.TerminationRequest;
import com.yxsk.relay.job.component.common.thread.WakeupAbleTask;
import com.yxsk.relay.job.component.common.utils.*;
import com.yxsk.relay.job.component.common.vo.SystemResourceInfo;
import com.yxsk.relay.job.component.endpoint.config.RelayJobAdminConfig;
import com.yxsk.relay.job.component.endpoint.config.RelayJobSlaveConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author 11376
 * @CreaTime 2019/6/15 19:41
 * @Description
 */
@Slf4j
public class SigningTask extends WakeupAbleTask {

    private long interval = TimeUnit.MILLISECONDS.convert(15, TimeUnit.SECONDS);

    private volatile boolean stop = false;

    private RemoteCaller remoteCaller;

    private RelayJobAdminConfig jobAdminConfig;

    private RelayJobSlaveConfig slaveConfig;

    public SigningTask(RelayJobAdminConfig jobAdminConfig, RelayJobSlaveConfig slaveConfig, RemoteCaller remoteCaller) {
        this.jobAdminConfig = jobAdminConfig;
        this.slaveConfig = slaveConfig;
        this.remoteCaller = remoteCaller;
    }

    public void shutdown() {
        this.stop = true;
        this.wakeup();

        try {
            this.workThread.join();
            // 签退
            this.signOut();
        } catch (InterruptedException e) {
            log.error("", e);
        }
    }

    @Override
    protected Object doWork() {
        while (!stop) {
            // 执行签约工作
            this.sign();
            if (!this.workThread.isInterrupted()) {
                try {
                    Thread.sleep(this.interval);
                } catch (InterruptedException e) {
                    log.info("Relay job signing interrupted.", e);
                }
            }
        }
        return "Relay job signing thread stop.";
    }

    private void signOut() {

        List<RelayJobAdminConfig.AddressInfo> addresses = this.jobAdminConfig.getAddresses();
        if (!CollectionUtils.isEmpty(addresses)) {
            addresses.stream().forEach(address -> {
                TerminationRequest request = getTerminationRequest();

                String url = this.buildTerminationUrl(address);
                try {
                    Map<String, String> header = null;
                    if (StringUtils.isNotEmpty(address.getToken())) {
                        header = new HashMap<>();
                        header.put(UriConstant.AUTHORIZATION_HEADER_KEY, address.getToken());
                    }
                    ResultResponse response = this.remoteCaller.call(request, header, url, ResultResponse.class);
                    if (response == null) {
                        log.error("Relay job sign out error, reason：{}", "response message is null");
                    }
                    if (!ResultResponse.SUCCESS_CODE.equals(response.getCode())) {
                        log.error("Relay job sign out error, response code：{}, message：{}", response.getCode(), response.getMessage());
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("Relay job sign out success.");
                        }
                    }
                } catch (Exception e) {
                    log.error(MessageFormat.format("Relay job sign out error, address: {0}", url), e);
                }
            });
        }

    }

    private String buildTerminationUrl(RelayJobAdminConfig.AddressInfo address) {
        return new StringBuilder("http://")
                .append(address.getHost())
                .append(":")
                .append(address.getPort())
                .append(UriConstant.TERMINATE_URI)
                .toString();
    }

    private void sign() {
        List<RelayJobAdminConfig.AddressInfo> addresses = this.jobAdminConfig.getAddresses();
        if (CollectionUtils.isEmpty(addresses)) {
            log.warn("Relay job admin host address not configured.");
            return;
        }

        addresses.stream().forEach(address -> {
            RenewalRequest request = getRenewalRequest();

            String url = buildRegistryUrl(address);
            try {
                Map<String, String> header = null;
                if (StringUtils.isNotEmpty(address.getToken())) {
                    header = new HashMap<>();
                    header.put(UriConstant.AUTHORIZATION_HEADER_KEY, address.getToken());
                }
                ResultResponse response = this.remoteCaller.call(request, header, url, ResultResponse.class);
                if (response == null) {
                    log.error("Relay job registry error, reason：{}", "response message is null");
                }
                if (!ResultResponse.SUCCESS_CODE.equals(response.getCode())) {
                    log.error("Relay job registry error, response code：{}, message：{}", response.getCode(), response.getMessage());
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Relay job sign success.");
                    }
                }
            } catch (Exception e) {
                log.error(MessageFormat.format("Relay job registry error, address: {0}", url), e);
            }
        });

    }

    protected RenewalRequest getRenewalRequest() {
        RenewalRequest request = new RenewalRequest();
        request.setAppName(this.slaveConfig.getAppName());
        request.setAuthToken(this.slaveConfig.getAuthToken());
        request.setNetProtocol(NetProtocol.HTTP.getProtocol());
        request.setHost(this.getHost());
        request.setPort(this.getPort());
        request.setRequestTime(DateUtils.getCurrentDate());
        request.setSerialNo(SerialNoUtils.nextId());
        request.setVersion(UriConstant.VERSION_NO);
        request.setResourceInfo(this.getSystemResource());
        return request;
    }

    private SystemResourceInfo getSystemResource() {
        // 内存信息：单位 MB
        return SystemResourceInfo.builder()
                .availableMemory(SystemInfoUtils.getJvmAvailableMemory(SystemInfoUtils.StoredRadix.MB))
                .freeMemory(SystemInfoUtils.getJvmFreeMemory(SystemInfoUtils.StoredRadix.MB))
                .totalMemory(SystemInfoUtils.getJvmTotalMemory(SystemInfoUtils.StoredRadix.MB))
                .coreNum(SystemInfoUtils.getJvmAvailableCores())
                .osArch(SystemInfoUtils.getOsArch())
                .osName(SystemInfoUtils.getOsName())
                .osVersion(SystemInfoUtils.getOsVersion())
                .build();
    }

    protected TerminationRequest getTerminationRequest() {
        TerminationRequest request = new TerminationRequest();
        request.setHost(this.getHost());
        request.setAppName(this.slaveConfig.getAppName());
        request.setReason("application shutdown.");
        request.setRequestTime(DateUtils.getCurrentDate());
        request.setSerialNo(SerialNoUtils.nextId());
        request.setVersion(UriConstant.VERSION_NO);
        return request;
    }

    private String buildRegistryUrl(RelayJobAdminConfig.AddressInfo address) {
        return new StringBuilder("http://")
                .append(address.getHost())
                .append(":")
                .append(address.getPort())
                .append(UriConstant.RENEWAL_URI)
                .toString();
    }

    private Integer getPort() {
        // 首先取配置的端口
        return this.slaveConfig.getPort();
    }

    private String getHost() {
        // 首先获取配置 IP
        if (StringUtils.isNotEmpty(this.slaveConfig.getHost())) {
            return this.slaveConfig.getHost();
        }
        // 然后取 本地 IP
        try {
            return NetUtils.getLocalIp();
        } catch (UnknownHostException e) {
            log.error("获取本地主机IP错误", e);
            throw new IllegalArgumentException("不能解析服务主机IP");
        }
    }

}
